# -*- coding: utf-8 -*-
"""
Created on Wed Dec 18 09:53:14 2019

@author: Lenovo
"""

import pandas as pd
import math
import time
import numpy as py
import csv


pd.set_option('display.max_rows', None)
start_time = time.time()
names = ['userId', 'movieId', 'rating', 'timestamp']
data = pd.read_csv('F:/collaborative deep learning/movie/ml-100k/u.data', 
                   sep='\t', names=names)
data = data.drop(['timestamp'], axis=1)
data = data.sort_values(['userId', 'movieId'], ascending=True)

def fd_df_rating(x, am):
#     print("x = ",x)
    return abs(x["rating"] - am)

def e_df_rating(x, am):
#     print("x = ",x)
    return sigmoid(x["rating"] - am)

def sigmoid(x):
    return 1 / (1 + math.exp(-x))

def cal_by_user(id):
    #loc——通过行标签索引行数据 
    #iloc——通过行号索引行数据 
    #ix——通过行标签或者行号索引行数据（基于loc和iloc 的混合） 
    rating_by_userid = data.loc[data['userId'].isin([id])]
    
    rating_by_userid_length = len(rating_by_userid)

    am = sum(rating_by_userid["rating"]) / (5 * rating_by_userid_length)
   
    # DataFrame.copy(deep=True)复制此对象的索引和数据。
    fd_df = rating_by_userid.copy(deep=True)
    # 对数据框（DataFrame）的数据进行按行或按列操作时用apply()
    # axis=1，表示按行对数据进行操作,lambda匿名函数相当于定义一个函数返回冒号后的部分
    fd_df["rating"] = rating_by_userid.apply(lambda x:fd_df_rating(x,am),axis=1)
    
    fd = sum(fd_df["rating"]) / (5 * rating_by_userid_length)
    
    e_df = rating_by_userid.copy(deep=True)
    e_df["rating"] = rating_by_userid.apply(lambda x:e_df_rating(x,am),axis=1)

    e = sum(e_df["rating"]) / (5 * rating_by_userid_length)
    
    al = am - (fd - e) / 2

    au = am + (fd - e) /2

    return [am, al, au]

user_num = data.loc[:,"userId"].max()

fuzzy_num = []
for i in range(1,user_num + 1):
    fuzzy_num.append(cal_by_user(i))
    
def sia(a, b):
    rst_up = 2 * (min(b[2],a[2]+(b[0]-a[0]) ) - max( a[1]+(b[0]-a[0]), b[1] ))
    rst_down = ( a[2]-a[1] + (b[2]-b[1]) )
    return rst_up / rst_down

a = 0.2
b = 1 - a
def siam(userid_1, userid_2):
#     print("userid_1 = {}".format(userid_1))
#     print("userid_2 = {}".format(userid_2))
    sia_val = sia(fuzzy_num[userid_1 - 1], fuzzy_num[userid_2 - 1])
    part_a = a * (1 - abs(fuzzy_num[userid_1-1][0]-fuzzy_num[userid_2-1][0]))
    part_b = b * sia_val
    return part_a + part_b


import numpy as np
names = ['user_id', 'item_id', 'rating', 'timestamp']
df = pd.read_csv('F:/collaborative deep learning/movie/ml-100k/u.data',
                 sep='\t', names=names)
df.head()

n_users = df.user_id.unique().shape[0]
n_items = df.item_id.unique().shape[0]
ratings = np.zeros((n_users, n_items))
for row in df.itertuples():
    ratings[row[1]-1, row[2]-1] = row[3]


print (str(n_users) + ' users')
print( str(n_items) + ' items')
sparsity = float(len(ratings.nonzero()[0]))
sparsity /= (ratings.shape[0] * ratings.shape[1])
sparsity *= 100
print( 'Sparsity: {:4.2f}%'.format(sparsity))



def train_test_split(ratings):
    test = np.zeros(ratings.shape)
    train = ratings.copy()
    for user in range(ratings.shape[0]):
        test_ratings = np.random.choice(ratings[user, :].nonzero()[0], 
                                        size=10, 
                                        replace=False)
        train[user, test_ratings] = 0.
        test[user, test_ratings] = ratings[user, test_ratings]
        
    # Test and training are truly disjoint
    assert(np.all((train * test) == 0)) 
    return train, test

train, test = train_test_split(ratings)

i_m = train.sum(axis = 0)/(train != 0).sum(axis = 0)
np.nan_to_num(i_m,copy=False)
u_m = train.sum(axis = 1)/(train != 0).sum(axis = 1)
#user std deviation
#u_m[:,None]大小为(943, 1)，None的作用就是在相应的位置上增加了一个维度，在这个维度上只有一个元素
#np.where(condition, x, y)当condition中的值为True时，选取x的值，否则从y中选取。
#当trainset中元素非0时，结果等于trainset中的值-用户打分均值，否则为0.
std_dev = np.where((train != 0), train - u_m[:,None], train)
std_dev = np.square(std_dev)
std_dev = std_dev.sum(axis = 1)/(train != 0).sum(axis = 1)
std_var = np.sqrt(std_dev)


#URP similarity

#numpy.broadcast_to 函数将数组广播到新形状。它在原始数组上返回只读视图。
#把943的u_m广播到（943,943）的tmp_urp
tmp_urp = np.broadcast_to(u_m, (n_users,n_users))
muu_muv = abs(tmp_urp - u_m[:,None])
tmp1_urp = np.broadcast_to(std_var, (n_users,n_users))
sigma_u_v = abs(tmp1_urp - std_var[:,None])
mul_urp = muu_muv * sigma_u_v
print("MUL IN URP MU*SIG")
sim_urp = 1 - (1 / (1 + np.exp(- mul_urp)))
#sim_urp = np.cos(mul_urp * np.pi/1.8)

def jacc_sim(trainset):
    t = time.time()
    x = np.where((trainset != 0),1,0)
    jiao = x.dot(x.T)
    print("JACC DOT")
    cha = x.sum(axis = 1)
    cha1 = np.broadcast_to(cha, (n_users, n_users))
    cha = cha1 * cha[:, None]
    sim = (jiao / cha)** 0.5
    #sim = jiao / cha
    
    y = time.time() - t
    print("JACCARD SIM TIME = ",y)
    return sim


sim_jacc = jacc_sim(train)
#from sklearn.neighbors import DistanceMetric
#
#dist = DistanceMetric.get_metric('jaccard')
#dist_matrix = dist.pairwise(train)
#sim_jacc = 1 - dist_matrix

rmed = 3

sim_pss = np.zeros((n_users, n_users))
def sigmoid_2(rul, rvl):
    return 1/(1 + np.exp(- abs(rul - rvl)))

def proximity(rul, rvl):
    pr = 1 - sigmoid_2(rul, rvl)
    return pr

def significance(rul, rvl):
    sig = 1/(1 + np.exp(-(abs(rul-rmed) * abs(rvl-rmed))))  
    return sig
def singularity(rul, rvl,i_m):
    tmp = abs(((rul + rvl)/2)- i_m)
    sign = 1 - 1/(1 + (np.exp(- tmp)))
    return sign
def PSS(rup, rvp, i_m):
    pr = proximity(rup, rvp)
    sig = significance(rup, rvp)
    sing = singularity(rup, rvp, i_m)
    return((pr * sig * sing).sum())

for i in range(n_users):
    nzi = np.nonzero(train[i])
    for j in range(i, n_users):
        nzj = np.nonzero(train[j])
        intr = np.intersect1d(nzi, nzj)
        sim_pss[i,j] = PSS(train[i,intr], train[j,intr], i_m[intr])
tmp = np.triu(sim_pss)
tmp = tmp.T + np.triu(sim_pss, k=1)
sim_pss = tmp

count = 0

write_num = user_num 

header = ["userId", "userId", "furry"]
with open("furry_user_sim_Similarity.csv", "w", newline='') as csvfile:
    writer = csv.writer(csvfile)
    # 先写入columns_name
    writer.writerow(header)
    for i in range(1, write_num + 1):
        if i % 1 == 0:
            print("当前是第{}个用户，共有{}个用户".format(i, write_num))
            process_time = time.time()
            print('目前已运行时间：', process_time - start_time, '秒')
        for j in range(1, user_num + 1):
            #user_siam = sim_pss[i-1][j-1] * sim_urp[i-1][j-1] * sim_jacc[i-1][j-1]
            #user_siam = siam(i, j) * sim_urp[i-1][j-1] * sim_jacc[i-1][j-1]
            user_siam = (siam(i, j) ** 0.5) * (sim_urp[i-1][j-1]** 0.5) * sim_jacc[i-1][j-1]
            if user_siam == 0:
                count += 1
            # 写入多行用writerows
            row = [i, j, user_siam]
            writer.writerow(row)